package com.ffe.flower.engine.component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.ffe.flower.engine.component.FlowInstance.ApplicantForm;
import com.ffe.flower.engine.component.FlowInstance.AuditorForm;
import com.ffe.flower.engine.component.FlowInstance.FlowEvent;
import com.ffe.flower.engine.constant.AuditStatus;
import com.ffe.flower.engine.dto.FlowDefinition;
import com.ffe.flower.engine.exception.FlowChangedException;
import com.ffe.flower.engine.exception.FlowEventIsIncorrectException;
import com.ffe.flower.engine.exception.FlowHasAlreadyFinishedException;
import com.ffe.flower.engine.exception.FlowNotFoundException;
import com.ffe.flower.engine.exception.ProcessNotFoundException;
import com.ffe.flower.engine.model.FlowInfo;
import com.ffe.flower.engine.service.IFlowInfoService;
import com.ffe.flower.engine.wrapper.FlowWrapper;

@Component
public class FlowEngineListener {

  @Autowired
  private IFlowInfoService flowInfoService;

  @EventListener
  public void onApplicationEvent(FlowEvent event) {
    Object field = event.getField();
    FlowDefinition startState = null;
    FlowInfo flowInfo = null;
    boolean isNewProcess = false;
    if (event.getProcessId() != null) {
      startState = ProcessEngine.findProcess(event.getProcessId());
      isNewProcess = true;
    } else {
      flowInfo = flowInfoService.get(event.getFlowId());
      if (flowInfo == null) {
        throw new FlowNotFoundException();
      }
      startState = ProcessEngine.findProcess(flowInfo.getProcessId());
    }

    if (startState == null) {
      throw new ProcessNotFoundException();
    }

    FlowWrapper flowWrapper = null;
    if (isNewProcess) {
      ApplicantForm dataForm = event.getApplicantForm();
      checkApplicantDataForm(dataForm);
      flowWrapper = flowInfoService.start(field, startState.getProcessId(), dataForm.getApplicantId(),
          dataForm.getTargetId(), startState.getFlowState(), dataForm.getPromoterId(), dataForm.isPromoterAgent());
    } else {
      AuditorForm dataForm = event.getAuditorForm();
      checkAuditorDataForm(dataForm);
      Integer flowId = flowInfo.getFlowId();
      if (flowInfo.isFinished()) {
        throw new FlowHasAlreadyFinishedException();
      }
      FlowDefinition curFlowDefinition = ProcessEngine.findProcessState(flowInfo.getProcessId(),
          flowInfo.getCurState());
      if (curFlowDefinition == null) {
        throw new FlowNotFoundException();
      }

      FlowDefinition target = curFlowDefinition.getNext(event.getEvent());
      if (target == null) {
        throw new FlowEventIsIncorrectException();
      }

      try {
        if (!target.isLast()) {
          flowWrapper = flowInfoService.transit(field, flowId, target.getFlowState(), dataForm.getAuditorId(),
              dataForm.isAuditorAgent(), dataForm.getRemark());
        } else {
          if (target.isFinished()) {
            flowWrapper = flowInfoService.transitToEnd(field, flowId, target.getFlowState(), dataForm.getAuditorId(),
                dataForm.isAuditorAgent(), AuditStatus.APPROVAL, dataForm.getRemark(), true);
          } else {
            flowWrapper = flowInfoService.transitToEnd(field, flowId, target.getFlowState(), dataForm.getAuditorId(),
                dataForm.isAuditorAgent(), AuditStatus.REJECT, dataForm.getRemark(), false);
          }
        }
      } catch (ObjectOptimisticLockingFailureException e) {
        throw new FlowChangedException();
      }
    }
    event.setFlowWrapperAfterEvent(flowWrapper);

  }

  private void checkApplicantDataForm(ApplicantForm dataForm) {
    Assert.notNull(dataForm, "ApplicantData must not be null");
    Assert.hasText(dataForm.getApplicantId(), "ApplicantId must not be empty");
    Assert.hasText(dataForm.getPromoterId(), "PromoterId must not be empty");
    Assert.hasText(dataForm.getTargetId(), "TargetId must not be empty");
  }

  private void checkAuditorDataForm(AuditorForm dataForm) {
    Assert.notNull(dataForm, "AuditorData must not be null");
    Assert.hasText(dataForm.getAuditorId(), "AuditorId must not be empty");
  }

}
